package com.xxx;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * rpc框架具体实现类
 */
public class RpcImpl implements Rpc {

	/**
	 * 暴露rpc服务
	 */
	@Override
	public void export(Object service, int port) throws IOException {
		// service不能为null
		if(service==null) {
			throw new IllegalArgumentException("service is null");
		}
		
		// port必须在(0, 65535]之内
		if(!(port>0 || port<=65535)) {
			throw new IllegalArgumentException("port is invalid, port is "+port);
		}
		
		System.out.println("service is "+service.getClass().getName()+", port is "+port);

		// 通过socket监听客户端请求
		ServerSocket serverSocket = new ServerSocket(port);
		for(;;) { // 服务端不断循环
			try {
				// 服务端通过socket接收客户端请求（阻塞）
				final Socket socket = serverSocket.accept();
				new Thread(() -> {
					try {
						try { 
							// 输入流处理： 从socket接收输入流InputStream，然后转换成ObjectInputStream
							ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
							try {
								// 从ObjectInputStream中读取报文
								String methodName = ois.readUTF(); // readUTF()是java专用的字符序列格式，读取的必须是writeUTF写下的字符串。
								Class<?>[] parameterTypes = (Class<?>[]) ois.readObject(); // 参数类型
								Object[] arguments  = (Object[]) ois.readObject(); // 参数值
								
								// 输出流处理： 从socket接收输出流OutputStream，然后转换成ObjectOutputStream
								ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
								try {
									Method method = service.getClass().getMethod(methodName, parameterTypes);
									Object result = method.invoke(service, arguments);
									oos.writeObject(result);
								} catch (Throwable t) {
									throw t;
								} finally {
									oos.close(); // 关闭ObjectOutputStream
								}
							} catch (Exception e) {
								throw e;
							} finally {
								ois.close(); // 关闭ObjectInputStream
							}
						} catch (Exception e) {
							e.printStackTrace();
						} finally {
							socket.close(); // 关闭socket
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}).start();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 调用rpc服务
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T invoke(Class<T> interfaceClass, String host, int port) {
		// interfaceClass不能为空
		if(interfaceClass==null) {
			throw new IllegalArgumentException("the interface is null");
		}
		
		// interfaceClass必须是interface
		if(!interfaceClass.isInterface()) {
			throw new IllegalArgumentException("the interface"+interfaceClass.getClass().getName() + " is not interface");
		}
		
		// host不能为空
		if(host==null || host.length()==0) {
			throw new IllegalArgumentException("host is null");
		}
		
		// port必须在(0, 65535]之内
		if(!(port>0 || port<=65535)) {
			throw new IllegalArgumentException("port is invalid, port is "+port);
		}
		
		System.out.println("GET remote service " + interfaceClass.getName() + "from server " + host + ":" + port);
		
		return (T) Proxy.newProxyInstance(
				interfaceClass.getClassLoader(), 
				new Class[] {interfaceClass}, 
				(proxy, method, args) -> {
					// 创建客户端和服务端的socket链接
					Socket socket = new Socket(host, port);
					try {
						// 从socket中获取输出流OutputStream，然后转换成ObjectOutputStream
						ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
						try {
							oos.writeUTF(method.getName()); // 方法名
							oos.writeObject(method.getParameterTypes()); // 参数类型
							oos.writeObject(args); // 具体参数
							
							// 从socket或获取输入流InputStream，然后转换成ObjectInputStream
							ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
							try {
								// 从ObjectInputStream读取方法返回结果
								Object result = ois.readObject();
								if(result instanceof Throwable) {
									throw (Throwable) result;
								}
								return result;
							} finally {
								ois.close(); // 关闭ObjectInputStream
							}
						} finally {
							oos.close(); // 关闭ObjectOutputStream
						}
					} finally {
						socket.close();// 关闭socket
					}
				});
	}

}
